﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Daybreak.Core.Rendering
{
    public abstract class VertexArray
    {
        public abstract class Generic<T> : VertexArray where T : struct, IEquatable<T>
        {
            public static readonly Type ItemType = typeof(T);

            /// <summary>
            /// Item size in bytes
            /// </summary>
            public static readonly int ItemSizeByte = Marshal.SizeOf(typeof(T));

            /// <summary>
            /// Item size in floats
            /// </summary>
            public static readonly int ItemSizeFloat = Marshal.SizeOf(typeof(T)) / 4;

            /// <summary>
            /// Default item set
            /// </summary>
            public T[] CurrentSet;

            /// <summary>
            /// Additional item set
            /// </summary>
            public T[] ItemSet0;

            /// <summary>
            /// Additional item set
            /// </summary>
            public T[] ItemSet1;

            /// <summary>
            /// Additional item set
            /// </summary>
            public T[] ItemSet2;

            public Generic(VertexElementUsage usage)
                : base(usage)
            {
            }

            public Generic(T[] arr, VertexElementUsage usage, int set)
                : base(usage)
            {
                SetItems(arr, set);
            }

            public override void SetItems(Array arr, int set)
            {
                // Up to 2 item sests per each VertexElementUsage 

                T[] newItems = arr as T[];

                if (set == 0)
                    ItemSet0 = newItems;

                if (set == 1)
                    ItemSet1 = newItems;

                if (set == 2)
                    ItemSet2 = newItems;

                if (CurrentSet == null)
                    CurrentSet = newItems;
            }

            public override Array GetItems(int set)
            {
                if (set == 0)
                    return ItemSet0;

                if (set == 1)
                    return ItemSet1;

                if (set == 2)
                    return ItemSet2;

                return null;
            }

            public override void SetCurrentSet(int set)
            {
                if (CurrentSet != null) // TODO bind vertex input sets
                    return; // at this moment we can use only first declared input set (will work in most cases)

                if (set == 0 && ItemSet0 != null)
                    CurrentSet = ItemSet0;

                if (set == 1 && ItemSet1 != null)
                    CurrentSet = ItemSet1;

                if (set == 2 && ItemSet2 != null)
                    CurrentSet = ItemSet2;

            }

            public override string ToString()
            {
                return string.Format("{0}:{1} Sets: {2}", Generic<T>.ItemType.Name, _usage, ItemSetsCount);
            }

            public override int Count
            {
                get
                {
                    if (CurrentSet == null)
                        return 0;

                    return CurrentSet.Length;
                }
            }

            public override int ItemSize
            {
                get { return Generic<T>.ItemSizeByte; }
            }

            public override int ItemSetsCount
            {
                get
                {
                    int result = 0;

                    if (ItemSet0 != null)
                        result++;

                    if (ItemSet1 != null)
                        result++;

                    if (ItemSet2 != null)
                        result++;

                    return result;
                }
            }
        }

        public class Colors : Generic<Color>
        {
            public Colors(Color[] arr, VertexElementUsage usage, int set)
                : base(arr, usage, set)
            {
            }

            internal unsafe override void CopyVertexTo(ref float* rawVertexData, int vIndex)
            {
                uint* adr = (uint*)rawVertexData++;
                *adr = CurrentSet[vIndex].PackedValue;
            }

            internal override void SetUpVertexInput(ref Drawable.Topology.InputOffset input, int offset, int set)
            {
                input.SetUsage<Color>(_usage, offset, set);
            }
        }

        public class Float2 : Generic<Vector2>
        {
            public Float2(Vector2[] arr, VertexElementUsage usage, int set)
                : base(arr, usage, set)
            {
            }

            internal unsafe override void CopyVertexTo(ref float* rawVertexData, int vIndex)
            {
                *rawVertexData++ = CurrentSet[vIndex].X;
                *rawVertexData++ = CurrentSet[vIndex].Y;
            }

            internal override void SetUpVertexInput(ref Drawable.Topology.InputOffset input, int offset, int set)
            {
                input.SetUsage<Vector2>(_usage, offset, set);
            }

        }

        public class Float3 : Generic<Vector3>
        {
            public Float3(Vector3[] arr, VertexElementUsage usage, int set)
                : base(arr, usage, set)
            {
            }

            internal unsafe override void CopyVertexTo(ref float* rawVertexData, int vIndex)
            {
                *rawVertexData++ = CurrentSet[vIndex].X;
                *rawVertexData++ = CurrentSet[vIndex].Y;
                *rawVertexData++ = CurrentSet[vIndex].Z;
            }

            internal override void SetUpVertexInput(ref Drawable.Topology.InputOffset input, int offset, int set)
            {
                input.SetUsage<Vector3>(_usage, offset, set);
            }

        }

        public class Float4 : Generic<Vector4>
        {
            public Float4(Vector4[] arr, VertexElementUsage usage, int set)
                : base(arr, usage, set)
            {
            }

            internal unsafe override void CopyVertexTo(ref float* rawVertexData, int vIndex)
            {
                *rawVertexData++ = CurrentSet[vIndex].X;
                *rawVertexData++ = CurrentSet[vIndex].Y;
                *rawVertexData++ = CurrentSet[vIndex].Z;
                *rawVertexData++ = CurrentSet[vIndex].W;
            }

            internal override void SetUpVertexInput(ref Drawable.Topology.InputOffset input, int offset, int set)
            {
                input.SetUsage<Vector4>(_usage, offset, set);
            }

        }

        protected VertexElementUsage _usage;

        public VertexArray(VertexElementUsage usage)
        {
            _usage = usage;
        }

        public abstract void SetItems(Array arr, int set);

        public abstract Array GetItems(int set);

        public abstract void SetCurrentSet(int set);


        /// <summary>
        /// Item count
        /// </summary>
        public abstract int Count { get; }

        /// <summary>
        /// Item size in bytes
        /// </summary>
        public abstract int ItemSize { get; }

        public abstract int ItemSetsCount { get; }

        public VertexElementUsage Usage
        {
            get { return _usage; }
        }

        internal unsafe abstract void CopyVertexTo(ref float* rawVertexData, int vIndex);

        internal abstract void SetUpVertexInput(ref Drawable.Topology.InputOffset input, int offset, int set);
    }
}
